#!/usr/bin/env python

import os, sys
import subprocess
import dbus, dbus.glib, dbus.service
import gobject
import pynotify
import gtk
import hashlib
import datetime

import locale
import gettext

if os.path.islink(__file__):
    this_file = os.readlink(__file__)
else:
    this_file = __file__
APP_DIR = os.path.realpath(os.path.dirname(this_file))

APP_NAME = 'TX'
LOCALE_DIR = 'locale'
locale.setlocale(locale.LC_ALL, '')
gettext.bindtextdomain(APP_NAME, LOCALE_DIR)
gettext.bind_textdomain_codeset(APP_NAME, 'UTF-8')
gettext.textdomain(APP_NAME)
_ = gettext.gettext

LOGO = os.path.join(APP_DIR, 'logo.png')

REPEAT_IN_MINUTES = datetime.timedelta(minutes=5)

def bad_time():
    return datetime.datetime(1911, 11, 11, 11, 11)

def whereis(program):
    for path in os.environ.get('PATH', '').split(':'):
        if os.path.exists(os.path.join(path, program)) and \
           not os.path.isdir(os.path.join(path, program)):
            return os.path.join(path, program)
    return None

MPLAYER = whereis('mplayer')

def play_alert(type):
    if type == 'enter':
        alert_file = os.path.join(APP_DIR, 'phone-outgoing-busy.ogg')
    elif type == 'renotify':
        alert_file = os.path.join(APP_DIR, 'phone-outgoing-busy.ogg')
    subprocess.Popen([MPLAYER, alert_file], shell=False)

class TxEntry:
    def __init__(self, time, message, iid=None):
        self._time = time
        self._time_last_alert = datetime.datetime.now()
        self._message = message
        self._already_shout = False

        self._id = iid
        if not iid:
            hash_seed = str(time) + message + str(datetime.datetime.now())
            self._id = hashlib.md5(hash_seed).hexdigest()

    def update(self, time, message):
        self._time = time
        self._message = message

class TXServer(dbus.service.Object):
    def __init__(self):
        name = dbus.service.BusName('org.auv.tx.Server', bus=dbus.SessionBus())
        dbus.service.Object.__init__(self, name, '/org/auv/tx/Server')

        # self.__txs[eid] = TxEntry
        self.__txs = {}

        # from 1, 0 means bad
        self.__next_eid = 1

        # last ExtID for entry, used for cancel commit
        self.__last_added = None

        self.__setupSystray()
        self.__load_database()
        self.__check_for_notify()

        # Not time critical, 10 seconds loop is enough
        self.timer = gobject.timeout_add(10000, self.__check_for_notify)

    def __load_database(self):
        f = None
        try:
            f = open(os.path.expanduser('~/.tx.setting'), 'r')
            lines = f.readlines()
            for line in lines:
                if len(line) < 50:
                    continue

                iid = line[0:32]
                nnnn = int(line[33:37])
                yy = int(line[38:40])
                rr = int(line[41:43])
                ss = int(line[44:46])
                ff = int(line[47:49])
                time = datetime.datetime(nnnn, yy, rr, ss, ff)
                message = line[50:].strip('\n').replace('\\n', '\n')
                e = TxEntry(time, message, iid)
                self.__txs[self.__next_eid] = e
                self.__next_eid += 1
        except:
            print '__load_database: load ~/.tx.setting failed'
        finally:
            if f:
                f.close()

    def __save_database(self):
        f = None
        try:
            f = open(os.path.expanduser('~/.tx.setting'), 'w')
            lines = []
            for k, entry in self.__txs.items():
                time = entry._time.strftime('%04Y-%02m-%02d %02H:%02M')
                message = entry._message.replace('\n', '\\n')
                lines.append(entry._id + ' ' + time + ' ' + message + '\n')
            f.writelines(lines)
        except:
            print '__save_database: load ~/.tx.setting failed'
        finally:
            if f:
                f.close()

    @dbus.service.method('org.auv.tx.Server')
    def queue(self, txid, time, message):
        txid = int(txid)

        orgtime = time
        time = datetime.datetime(int(time[0:4]), int(time[5:7]), int(time[8:10]), int(time[11:13]), int(time[14:16]))

        if time != bad_time():
            if self.__txs.has_key(txid):
                e = self.__txs[txid]
                self.__last_added = txid
            else:
                e = TxEntry(time, message)
                self.__txs[self.__next_eid] = e
                self.__last_added = self.__next_eid
                e.update(time, message)

            title = _('TX added or updated!')
            notify = _('\n\nID: %d\nTime: %s\n\nMessage: %s') % (self.__last_added, time.strftime('%04Y-%02m-%02d (%w) %02H:%02M'), message)
        else:
            title = _('TX update error!')
            notify = _('\n\nInvalid time format:\n  %s') % orgtime

        self.notify(notify, title)
        self.__next_eid += 1
        self.__save_database()

    @dbus.service.method('org.auv.tx.Server')
    def dump(self):
        ret = []
        for txid, entry in self.__txs.items():
            ret.append([txid, entry._time.strftime('%04Y-%02m-%02d (%w) %02H:%02M'), entry._message.replace('\n', '\\n')])
        return str(ret)

    @dbus.service.method('org.auv.tx.Server')
    def remove(self, txid):
        ''' txid ensure which entry removed, if -1 given, will remove the last appened '''
        txid = int(txid)
        if txid == -1 and self.__last_added:
            txid = self.__last_added

        if self.__txs.has_key(txid):
            entry = self.__txs[txid]

            title = _('TX delete success!')
            notify = _('\n\nID: %d\nTime: %s\n\nMessage: %s') % (txid, entry._time.strftime('%04Y-%02m-%02d (%w) %02H:%02M'), entry._message)

            del self.__txs[txid]
            self.__save_database()
        else:
            title = _('TX delete fail!')
            notify = _('\n\nEntry with ID = %d not found.') % txid

        self.notify(notify, title)

    def __check_for_notify(self):
        now = datetime.datetime.now()

        tips = ''
        shout_count = 0
        renotify_count = 0
        for txid, entry in self.__txs.items():
            if entry._time < now:
                # it's show time
                tips += '+%d: %s\n' % (txid, entry._message)

                # show notify for the first time
                if not entry._already_shout:
                    entry._already_shout = True
                    shout_count += 1

                    title = _('NOW! Job switches!')
                    notify = _('\n\nID: %d\nTime: %s\n\nMessage: %s') % (txid, entry._time.strftime('%04Y-%02m-%02d (%w) %02H:%02M'), entry._message)
                    self.notify(notify, title)

                # Renotify is needed
                if now - entry._time_last_alert > REPEAT_IN_MINUTES:
                    entry._time_last_alert = now
                    renotify_count += 1

                    title = _('Come back to REAL work!')
                    notify = _('\n\nID: %d\nTime: %s\n\nMessage: %s') % (txid, entry._time.strftime('%04Y-%02m-%02d (%w) %02H:%02M'), entry._message)
                    self.notify(notify, title)

        if shout_count:
            self.__systray.set_blinking(True)
            play_alert('enter')
        else:
            self.__systray.set_blinking(False)
            if renotify_count:
                play_alert('renotify')

        self.__systray.set_tooltip(tips)
        return True

    def __setupSystray(self):
        self.__systray = gtk.StatusIcon()

        self.__systray.set_from_file(LOGO)
        self.__systray.set_visible(True)

    def notify(self, message, title=None):
        pynotify.init('TX')
        noti = pynotify.Notification(' ', ' ')
        noti.set_urgency(pynotify.URGENCY_CRITICAL)

        noti.update(title, message, LOGO)
        noti.set_timeout(5000)
        noti.show()

    def run(self):
        loop = gobject.MainLoop()
        loop.run()

class TXClient:
    def __init__(self):
        bus = dbus.SessionBus()
        self.txService = bus.get_object('org.auv.tx.Server', '/org/auv/tx/Server')

    def queue(self, eid, time, message):
        queue = self.txService.get_dbus_method('queue', 'org.auv.tx.Server')
        #def queue(self, txid, time, message):
        try:
            queue(eid, time.strftime('%04Y-%02m-%02d %02H:%02M'), message)
        except Exception, e:
            pass

    def dump(self):
        dump = self.txService.get_dbus_method('dump', 'org.auv.tx.Server')
        reminders = eval(dump())

        #ret.append([txid, entry._time, entry._message])
        print '|', 'id'.center(4), '|', 'exp'.center(16), '|', 'msg'
        if len(reminders) > 0:
            for r in reminders:
                print '|', str(r[0]).center(4), '|', str(r[1]).center(16), '|', str(r[2])
        else:
            print ' --- no reminder exist ---'

    def remove(self, txid):
        remove = self.txService.get_dbus_method('remove', 'org.auv.tx.Server')
        remove(txid)

def show_usage():
    print 'Usage (Server mode): TX -d'
    print 'Usage (Client mode):'
    print '    TX z time msg    Add alert       time<[+j]digital>, msg<string>'
    print '    TX s txid        Delete alert    remid<digital>'
    print '    TX c             Dump alert'

def parse_time(time):
    ''' Parse the given time string, if valid return '1111-11-11 11:11' as bad time '''

    if time[0] == '+' or time[0] == 'j':
        try:
            int(time[1:])
        except:
            return bad_time()

        nnnn = yy = rr = ss = ff = 0
        try:
            ff = int(time[-2:]) or 0
            ss = int(time[-4:-2]) or 0
            rr = int(time[-6:-4]) or 0
            yy = int(time[-8:-6]) or 0
            nnnn = int(time[-12:-8]) or 0
        except:
            pass

        # delta only for day or less, currently
        return datetime.datetime.now() + datetime.timedelta(days=rr, hours=ss, minutes=ff)
    else:
        try:
            int(time)
        except:
            return bad_time()

        nnnn = yy = rr = ss = ff = -1
        try:
            if time[-2:] != '':
                ff = int(time[-2:])
            if time[-4:-2] != '':
                ss = int(time[-4:-2])
            if time[-6:-4] != '':
                rr = int(time[-6:-4])
            if time[-8:-6] != '':
                yy = int(time[-8:-6])
            if time[-12:-8] != '':
                nnnn = int(time[-12:-8])
        finally:
            now = datetime.datetime.now()
            if nnnn == -1:
                nnnn = now.year
            if yy == -1:
                yy = now.month
            if rr == -1:
                rr = now.day
            if ss == -1:
                ss = now.hour
            if ff == -1:
                ff = now.minute

        return datetime.datetime(nnnn, yy, rr, ss, ff)

if __name__ == '__main__':
    argc = len(sys.argv)
    if argc > 1:

        # -d for daemon mode
        if sys.argv[1] == '-d':
            s = TXServer()
            s.run()
            sys.exit(0)

        # z for Zengjia
        # eg. tx z [+]NNNNYYRRSSFF messages messages messages
        if sys.argv[1] == 'z':
            if argc >= 4:
                time = parse_time(sys.argv[2])

                try:
                    c = TXClient()
                    message = ' '.join(sys.argv[3:])
                    c.queue(0, time, message)
                except dbus.exceptions.DBusException:
                    print 'TX Server not running, try command "TX -d" first.'
                finally:
                    sys.exit(0)
        elif sys.argv[1] == 's':
            ''' if id not given delete the last '''
            if argc >= 3:
                for eid in sys.argv[2:]:
                    try:
                        if eid in ['e', 'E', 'm', 'M']:
                            eid = -1
                        int(eid)
                    except:
                        continue

                    try:
                        c = TXClient()
                        c.remove(eid)
                    except dbus.exceptions.DBusException:
                        print 'TX Server not running, try command "TX -d" first.'
                sys.exit(0)

        elif sys.argv[1] == 'c':
            try:
                c = TXClient()
                c.dump()
            except dbus.exceptions.DBusException:
                print 'TX Server not running, try command "TX -d" first.'
            finally:
                sys.exit(0)
    show_usage()

